#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dcompiler.h>
#include <D3dx9math.h>
#include <D3DVoxelChunkManager.h>
#include <D3DShaderCache.h>
D3DVoxelChunkManager::D3DVoxelChunkManager(ID3D11Device *pDevice, ID3D11DeviceContext *pImmediateContext)
{
	m_pVertexShaderMC = NULL;
	m_pGeometryShaderMC = NULL;

	m_pVertexLayout = NULL;
	m_pVoxelMeshLayout = NULL;

	m_pVertexShader = NULL;
	m_pPixelShader = NULL;
	

	m_pVoxelTextureSRV = NULL;
	m_pVoxelSamplerLinear = NULL;

	m_pTextureRV = NULL;
	m_pSamplerLinear = NULL;

	m_pVertexBuffer = NULL;
	m_pIndexBuffer = NULL;

	m_pDevice = pDevice;
	m_pImmediateContext = pImmediateContext;

}



HRESULT D3DVoxelChunkManager::InitMCShaders(void)
{
	HRESULT hr = S_OK;
    // Compile the vertex shader
    ID3DBlob* pVSBlob = NULL;
    hr = CompileShaderFromFile( L"MarchingCubes_.fx", "VS", "vs_5_0", &pVSBlob );
    if( FAILED( hr ) )
    {
        MessageBox( NULL,
                    L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK );
        return hr;
    }
	
    // Create the vertex shader
	
    hr = m_pDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &m_pVertexShaderMC );
    if( FAILED( hr ) )
    {    
        pVSBlob->Release();
        return hr;
    }

    // Define the input layout
    D3D11_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };
    UINT numElements = ARRAYSIZE( layout );

    // Create the input layout
    hr = m_pDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(),
                                          pVSBlob->GetBufferSize(), &m_pVertexLayout );
    pVSBlob->Release();
    if( FAILED( hr ) )
        return hr;

    // Set the input layout
    m_pImmediateContext->IASetInputLayout( m_pVertexLayout );
	

	// Compile the geometry shader
    ID3DBlob* pGSBlob = NULL;
    hr = CompileShaderFromFile( L"MarchingCubes_.fx", "GS", "gs_5_0", &pGSBlob );
    if( FAILED( hr ) )
    {
        MessageBox( NULL,
                    L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK );
        return hr;
    }
	unsigned char *pp =(unsigned char *) pGSBlob->GetBufferPointer();

    

	// create a geometry shader without stream-out
	//hr = pDevice->CreateGeometryShader( pGSBlob->GetBufferPointer(), pGSBlob->GetBufferSize(), NULL, &m_pGeometryShaderMC );
	// create stream-out declaration
	D3D11_SO_DECLARATION_ENTRY pDescSODecl []= 
	{
        { 0, "SV_POSITION", 0, 0, 4, 0},
		{ 0, "NORMAL", 0, 0, 3, 0},
        { 0, "TEXCOORD", 0, 0, 2, 0},
    };
	UINT strides[1] = {sizeof( pDescSODecl )};
	// create stream-out shader
	hr = m_pDevice->CreateGeometryShaderWithStreamOutput( pGSBlob->GetBufferPointer(), pGSBlob->GetBufferSize(), pDescSODecl, 3, strides, 1, 1, NULL, &m_pGeometryShaderMC );

    
    if( FAILED( hr ) )
        return hr;

	pGSBlob->Release();


    return hr;

}


HRESULT D3DVoxelChunkManager::InitRegularShaders(void)
{
	HRESULT hr = S_OK;
    // Compile the vertex shader
    ID3DBlob* pVSBlob = NULL;
    hr = CompileShaderFromFile( L"RenderVoxel.fx", "VS", "vs_5_0", &pVSBlob );
    if( FAILED( hr ) )
    {
        MessageBox( NULL,
                    L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK );
        return hr;
    }
	
    // Create the vertex shader
    hr = m_pDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &m_pVertexShader );
    if( FAILED( hr ) )
    {    
        pVSBlob->Release();
        return hr;
    }

	// Create the input layout

    D3D11_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 28, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };
    UINT numElements = ARRAYSIZE( layout );
    hr = m_pDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(),
                                          pVSBlob->GetBufferSize(), &m_pVoxelMeshLayout );
    pVSBlob->Release();
    if( FAILED( hr ) )
        return hr;

	// Compile the pixel shader
    ID3DBlob* pPSBlob = NULL;
    hr = CompileShaderFromFile( L"RenderVoxel.fx", "PS", "ps_5_0", &pPSBlob );
    if( FAILED( hr ) )
    {
        MessageBox( NULL,
                    L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK );
        return hr;
    }

    // Create the pixel shader
    hr = m_pDevice->CreatePixelShader( pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &m_pPixelShader );


    return hr;

}
void D3DVoxelChunkManager::Release(void)
{

	if( m_pVertexShaderMC ) m_pVertexShaderMC ->Release();
	if( m_pGeometryShaderMC ) m_pGeometryShaderMC->Release();

	if( m_pVertexShader ) m_pVertexShader ->Release();
	if( m_pPixelShader ) m_pPixelShader->Release();

	if( m_pVertexLayout ) m_pVertexLayout->Release();

	if( m_pVoxelTextureSRV ) m_pVoxelTextureSRV->Release();
	if( m_pVoxelSamplerLinear ) m_pVoxelSamplerLinear->Release();

	if( m_pVertexBuffer ) m_pVertexBuffer->Release();
	if( m_pIndexBuffer ) m_pIndexBuffer->Release();

	if(  m_pTextureRV ) m_pTextureRV->Release();
	if( m_pSamplerLinear ) m_pSamplerLinear->Release();

}
HRESULT D3DVoxelChunkManager::InitSharedBuffers()
{
	HRESULT hr;

	 // Load the Texture
    hr = D3DX11CreateShaderResourceViewFromFile( m_pDevice, L"seafloor.dds", NULL, NULL, &m_pTextureRV, NULL );
    if( FAILED( hr ) )
        return hr;

    // Create the sample state
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory( &sampDesc, sizeof(sampDesc) );
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    hr = m_pDevice->CreateSamplerState( &sampDesc, &m_pSamplerLinear );
    if( FAILED( hr ) )
        return hr;

	// Create the sample state
    D3D11_SAMPLER_DESC voxelSampDesc;
    ZeroMemory( &voxelSampDesc, sizeof(voxelSampDesc) );
    voxelSampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
    voxelSampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    voxelSampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    voxelSampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    voxelSampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    voxelSampDesc.MinLOD = 0;
    voxelSampDesc.MaxLOD = 1;
    hr = m_pDevice->CreateSamplerState( &voxelSampDesc, &m_pVoxelSamplerLinear );
    if( FAILED( hr ) )
        return hr;

	// Create vertex buffer
	VoxelVertexPlaceholder* pVertices = new VoxelVertexPlaceholder[D3DVoxelConstants::VoxelsPerChunkAxisCube];
	int k = D3DVoxelConstants::VoxelsPerChunkAxis;
	for(int i = 0; i < D3DVoxelConstants::VoxelsPerChunkAxis; i++ )
	{
		for(int j = 0; j < D3DVoxelConstants::VoxelsPerChunkAxis; j++ )
		{
			for(int k = 0; k < D3DVoxelConstants::VoxelsPerChunkAxis; k++ )
			{
				pVertices[D3DVoxelConstants::CalcVoxelIndex(i, j, k)].Pos = D3DXVECTOR3( i, j, k);
				pVertices[D3DVoxelConstants::CalcVoxelIndex(i, j, k)].Tex = D3DXVECTOR2( 0.0f, 1.0f);

			} 
		}
	} 
    /*{
        { XMFLOAT3( 0.0f, 1.0f, 0.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3( 1.0f, 1.0f, 0.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( 0.0f, 1.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

        { XMFLOAT3( 0.0f, 0.0f, 0.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3( 1.0f, 0.0f, 0.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3( 1.0f, 0.0f, 1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( 0.0f, 0.0f, 1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

    };*/

    D3D11_BUFFER_DESC bd;
    ZeroMemory( &bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;
	int zsdgfg = D3DVoxelConstants::VoxelsPerChunkAxisCube;
	bd.ByteWidth = sizeof( VoxelVertexPlaceholder ) * D3DVoxelConstants::VoxelsPerChunkAxisCube;
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory( &InitData, sizeof(InitData) );
    InitData.pSysMem = pVertices;
    hr = m_pDevice->CreateBuffer( &bd, &InitData, &m_pVertexBuffer );

    if( FAILED( hr ) )
        return hr;

    // Set vertex buffer
    UINT stride = sizeof( VoxelVertexPlaceholder );
    UINT offset = 0;
    m_pImmediateContext->IASetVertexBuffers( 0, 1, &m_pVertexBuffer, &stride, &offset );

    // Create index buffer
    
    WORD indices[] =
    {
        3,1,0,
        2,1,3,

        6,4,5,
        7,4,6,

        11,9,8,
        10,9,11,

        14,12,13,
        15,12,14,

        19,17,16,
        18,17,19,

        22,20,21,
        23,20,22
    };

    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof( WORD ) * 36;
    bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags = 0;
    InitData.pSysMem = indices;
    hr = m_pDevice->CreateBuffer( &bd, &InitData, &m_pIndexBuffer );
    return hr;
}


HRESULT D3DVoxelChunkManager::Render(D3DVoxelChunk * pChunk, ID3D11Buffer *pViewMatrixBuffer, ID3D11Buffer *pProjectionMatrixBuffer, ID3D11Buffer *pWorldMatrixBuffer)
{
	if( pChunk->m_pVoxelMeshStagingBuffer != NULL)
	{
		D3D11_MAPPED_SUBRESOURCE ms;
		m_pImmediateContext->CopyResource(pChunk->m_pVoxelMeshStagingBuffer, pChunk->m_pVoxelMeshBuffer);

		if(m_pImmediateContext->Map(pChunk->m_pVoxelMeshStagingBuffer, NULL, D3D11_MAP_READ, NULL, &ms) == S_OK)
		{
			struct SOStruct
			{
				float pos[4];
				float normal[3];
				float uv[2];
				float padding[3];
			};
			SOStruct *sodata = (SOStruct *)ms.pData;
			int k = sizeof(SOStruct);

			m_pImmediateContext->Unmap(pChunk->m_pVoxelMeshStagingBuffer, NULL);   
			
		}
	}

	// set the stream-out buffer
	ID3D11Buffer* soBufferList[1] = {NULL};
	UINT soBufferOfs[1] = {0};
	m_pImmediateContext->SOSetTargets(0, NULL, NULL);

	// set the stream-out buffer
	ID3D11Buffer* vertexBufferList[1] = {pChunk->m_pVoxelMeshBuffer};

	// Set the input layout
    m_pImmediateContext->IASetInputLayout( m_pVoxelMeshLayout );

	// Set vertex buffer
    UINT stride = sizeof( VoxelStreamOutMeshFormat );
    UINT offset = 0;
    m_pImmediateContext->IASetVertexBuffers( 0, 1, vertexBufferList, &stride, &offset );

	m_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
	m_pImmediateContext->VSSetShader( m_pVertexShader, NULL, 0 );
	//// use the chunk specific matrices.
	m_pImmediateContext->VSSetConstantBuffers( 0, 1, &pViewMatrixBuffer );
    m_pImmediateContext->VSSetConstantBuffers( 1, 1, &pProjectionMatrixBuffer );
    m_pImmediateContext->VSSetConstantBuffers( 2, 1, &pWorldMatrixBuffer );

	// use the same geometry shader but bind a different voxel data texture.
	m_pImmediateContext->GSSetShader( NULL, NULL, 0 );


    m_pImmediateContext->PSSetShader( m_pPixelShader, NULL, 0 );
	
    m_pImmediateContext->PSSetShaderResources( 0, 1, &m_pTextureRV );
    m_pImmediateContext->PSSetSamplers( 0, 1, &m_pSamplerLinear );

	m_pImmediateContext->DrawAuto();//(D3DVoxelConstants::VoxelsPerChunkAxisCube/100,0);
	m_pImmediateContext->IASetVertexBuffers( 0, 1, soBufferList, &stride, &offset );


	return S_OK;
}
HRESULT D3DVoxelChunkManager::ComputeIsosurface(D3DVoxelChunk * pChunk)
{
	// set the stream-out buffer
	ID3D11Buffer* soBufferList[1] = {pChunk->m_pVoxelMeshBuffer};
	UINT soBufferOfs[1] = {0};
	m_pImmediateContext->SOSetTargets(1, soBufferList, soBufferOfs);


	// Set the input layout
    m_pImmediateContext->IASetInputLayout( m_pVertexLayout );

	// Set vertex buffer
    UINT stride = sizeof( VoxelVertexPlaceholder );
    UINT offset = 0;
    m_pImmediateContext->IASetVertexBuffers( 0, 1, &m_pVertexBuffer, &stride, &offset );

	m_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_POINTLIST );
	m_pImmediateContext->VSSetShader( m_pVertexShaderMC, NULL, 0 );

	// use the same geometry shader but bind a different voxel data texture.
	m_pImmediateContext->GSSetShader( m_pGeometryShaderMC, NULL, 0 );
	m_pImmediateContext->GSSetShaderResources( 1, 1, &(pChunk->m_pVoxelTextureSRV ));
	m_pImmediateContext->GSSetSamplers( 1, 1, &m_pVoxelSamplerLinear );



    /*m_pImmediateContext->PSSetShader( m_pPixelShaderMC, NULL, 0 );
	
	
    m_pImmediateContext->PSSetShaderResources( 0, 1, &m_pTextureRV );
    m_pImmediateContext->PSSetSamplers( 0, 1, &m_pSamplerLinear );*/
	m_pImmediateContext->PSSetShader( NULL, NULL, 0 );

	m_pImmediateContext->Draw(D3DVoxelConstants::VoxelsPerChunkAxisCube,0);
	pChunk->m_bMarkedDirty = false;
	return S_OK;
	
}